/*
 +-----------------------------------------------------------------------------------------------+
 | Project : UKAPI
 +-----------------------------------------------------------------------------------------------+
 | Project Description : Universal & Kernel API
 +-----------------------------------------------------------------------------------------------+
 | File Description :
 |   TCP driver for Win32
 |                                                 
 +-----------------------------------------------------------------------------------------------+
 | Licence : MIT Licence
 +-----------------------------------------------------------------------------------------------+
 | Copyright (c) 2006 Olivier Vanhoucke
 | 
 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software 
 | and associated documentation files (the "Software"), to deal in the Software without 
 | restriction, including without limitation the rights to use, copy, modify, merge, publish, 
 | distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the 
 | Software is furnished to do so, subject to the following conditions:
 | 
 | The above copyright notice and this permission notice shall be included in all copies or 
 | substantial portions of the Software.
 | 
 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
 | BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 
 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 +-----------------------------------------------------------------------------------------------+
*/

#include <winsock2.h>

typedef enum
{
	socket_mode_client_e,
	socket_mode_server_e,
} socket_mode_t;

typedef struct
{
	SOCKET				socket;
	uint16_t			port;
	struct sockaddr_in	address;
	socket_mode_t		socket_mode;
	fd_set				readSet;
	fd_set				writeSet;
	bool_t				bInitialized;
	//infos clientS ???? pour le accept
} tcp_instance_context_t, * ptcp_instance_context_t;

typedef struct
{
	WSADATA		data;
} tcp_device_context_t, * ptcp_device_context_t;

/**
 * Inclusions standards
 */
#include <ukapi.h>

/**
 * Inclusions locales
 */
#include <stream_driver.h>

result_t tcp_init( pstream_driver_t device )
{
	ptcp_device_context_t pdevice_context = null;

	if ( device == null )
	{
		return result_invalidargument_e;
	}

	if ( device->device_context != null )
	{
		// driver already registered
		return result_abord_e;
	}

	pdevice_context = (ptcp_device_context_t) memory_allocate( sizeof( tcp_device_context_t ) );

	if ( pdevice_context == null )
	{
		return result_insufficient_memory_e;
	}

	memory_zero( pdevice_context, sizeof( tcp_device_context_t ) );

	device->device_context = pdevice_context;

	if ( WSAStartup( MAKEWORD( 2, 2 ), &pdevice_context->data ) != 0 )
	{
		return result_fail_e;
	}

	if (	( LOBYTE( device_context->data.wVersion ) != 2 ) 
		||	( HIBYTE( device_context->data.wVersion ) != 2 )
		)
	{
		WSACleanup();
		return result_fail_e;
	}

	return result_ok_e;
}

result_t tcp_deinit( pstream_driver_t device )
{
	ptcp_device_context_t pdevice_context = null;

	if ( device == null )
	{
		return result_invalidargument_e;
	}

	pdevice_context = (ptcp_device_context_t) device->device_context;

	if ( pdevice_context == null )
	{
		return result_invalidargument_e;
	}

	WSACleanup();

	memory_free( pdevice_context );

	return result_ok_e;
}

result_t tcp_open( pstream_driver_t device )
{
	ptcp_device_context_t	pdevice_context		= null;
	ptcp_instance_context_t pinstance_context	= null;

	if ( device == null )
	{
		return result_invalidargument_e;
	}

	pdevice_context = (ptcp_device_context_t) device->device_context;

	if ( pdevice_context == null )
	{
		return result_invalidargument_e;
	}

	pinstance_context = (ptcp_instance_context_t) memory_allocate( sizeof( tcp_instance_context_t ) );

	if ( pinstance_context == null )
	{
		return result_insufficient_memory_e;
	}

	memory_zero( pinstance_context, sizeof( tcp_instance_context_t ) );

	device->instance_context = pinstance_context;

	pinstance_context->bInitialized = true;

	return result_ok_e;
}

result_t tcp_close( pstream_driver_t device )
{
	ptcp_instance_context_t pinstance_context = (ptcp_context_t) device->instance_context;

	if (	( device == null )
		||	( pinstance_context == null )
		)
	{
		return result_invalidargument_e;
	}

	if ( pinstance_context->bInitialized == true )
	{
		shutdown( pinstance_context->socket, 2 );
		closesocket( pinstance_context->socket );
		pinstance_context->bInitialized = false;
	}

	memory_free( pinstance_context );

	return result_ok_e;
}

result_t tcp_read( pstream_driver_t device, pbuffer_t pbuffer )
{
	ptcp_instance_context_t pinstance_context = (ptcp_context_t) device->instance_context;
	register int len, size;
	unsigned char * ptr;

	if (	( device == null )
		||	( pinstance_context == null )
		||	( pinstance_context->bInitialized == false )
		)
	{
		return result_invalidargument_e;
	}

	size = pbuffer->szSize;
	ptr = pbuffer->ucPtr;

	while ( size > 0 )
	{
		len = recv ( pinstance_context->socket, ptr, (int) size, 0 );

		if ( len <= 0 )
		{
			return ((WSAGetLastError () == WSAECONNABORTED)?SOCKET_ERR_RECEIVE_CONNABORTED:SOCKET_ERR_RECEIVE);
		}

		size -= (unsigned long) len;
  		ptr += len;
	}

	return result_ok_e;
}

result_t tcp_write( pstream_driver_t device, pbuffer_t pbuffer )
{
	ptcp_instance_context_t pinstance_context = (ptcp_context_t) device->instance_context;
	register int len, size;
	unsigned char * ptr;

	if (	( device == null )
		||	( pinstance_context == null )
		||	( pinstance_context->bInitialized == false )
		)
	{
		return result_invalidargument_e;
	}

	size = pbuffer->szSize;
	ptr = pbuffer->ucPtr;

	while ( size > 0 )
	{
		len = send( pinstance_context->socket, ptr, (int) size, 0 );

		if ( len <= 0 )
		{
			return ((WSAGetLastError () == WSAECONNABORTED)?SOCKET_ERR_SEND_CONNABORTED:WSAGetLastError ());
		}

		size -= (unsigned long) len;
		ptr += len;
	}

	return result_ok_e;
}

result_t tcp_iocontrol( pstream_driver_t device, uint32_t command, pbuffer_t pbuffer_in, pbuffer_t pbuffer_out )
{
	ptcp_instance_context_t pinstance_context = (ptcp_context_t) device->instance_context;

	if (	( device == null )
		||	( pinstance_context == null )
		||	( pinstance_context->bInitialized == false )
		)
	{
		return result_invalidargument_e;
	}

	switch( command )
	{
	case 0: //set no delay
		{
			int	optval;

			switch (on)
			{
				case API_TRUE:
					optval = 1;
					break;

				case API_FALSE:
					optval = 0;
					break;

				default:
					return (SOCKET_ERR_SETNODELAY_INVAL);
			}

			if ( setsockopt( pinstance_context->socket, IPPROTO_TCP, TCP_NODELAY, (char *)&optval, sizeof( int ) ) != 0 )
			{
				return (SOCKET_ERR_SETNODELAY);
			}
		}
		break;

	case 1: //blocking mode
		{
			unsigned long	*flag;

			switch (on)
			{
				case API_TRUE:
					flag = (unsigned long *) 0;
					break;

				case API_FALSE:
					flag = (unsigned long *) 1;
					break;

				default:
					return (SOCKET_ERR_SETBLOCKINGMODE_INVAL);
			}

			if (ioctlsocket ((SOCKET) infos->soi_fd,FIONBIO,flag) != 0)
				return (SOCKET_ERR_SETBLOCKINGMODE);
		}
		break;
	case 2: // connect
		{
			if (	( pinstance_context->socket_mode != socket_mode_client_e ) 
				&&	( pinstance_context->socket_mode != socket_mode_server_e )
				)
			{
				return result_fail_e;
			}

			pinstance_context->socket = socket( AF_INET, SOCK_STREAM, 0 );

			if ( pinstance_context->socket == -1 )
			{
				return result_fail_e;
			}

			pinstance_context->address.sin_family	= AF_INET;
			pinstance_context->address.sin_port		= htons( (u_short) pinstance_context->port );

			if ( pinstance_context->socket_mode == socket_mode_server_e )
			{
				int optval;

				setsockopt(	pinstance_context->socket, SOL_SOCKET, SO_REUSEADDR, (char *)&optval, sizeof( int ) );

				pinstance_context->address.sin_addr.s_addr = htonl( INADDR_ANY );

				if ( bind( pinstance_context->socket, (struct sockaddr *)&pinstance_context->address, sizeof( struct sockaddr_in ) ) == -1 )
				{
					closesocket( pinstance_context->socket );
					return result_fail_e;
				}

				if ( listen( pinstance_context->socket, 1 ) == -1 )
				{
					closesocket( pinstance_context->socket );
					return result_fail_e;
				}
			}
			else /* socket_mode_client_e */
			{
				if ( SOCKET_GetHostName (params->sop_name, &pinstance_context->address.sin_addr.s_addr ) != API_OK )
				{
					closesocket( pinstance_context->socket );
					return result_fail_e;
				}

				if ( connect( pinstance_context->socket, (struct sockaddr *)&pinstance_context->address, sizeof( struct sockaddr_in ) ) == -1 )
				{
					closesocket( pinstance_context->socket );
					return result_fail_e;
				}
			}

			FD_ZERO( &pinstance_context->readSet );
			FD_ZERO( &pinstance_context->writeSet );
			FD_SET( pinstance_context->socket, &pinstance_context->readSet );
			FD_SET( pinstance_context->socket, &pinstance_context->writeSet );
		}
		break;
	case 3: //wait connect
		{
			//int SOCKET_WaitClient (	register SOCKET_INFO *infos, register SOCKET_INFO *result, int flag)
			int		length;

			result->soi_open = API_FALSE;

			if (infos->soi_open == API_FALSE)
				return (SOCKET_ERR_WAITCLIENT_INVAL);

			if (infos->soi_mode == SOCKET_CLIENT)
				return (SOCKET_ERR_WAITCLIENT_INVAL);

			if (flag & API_IO_NOWAIT)
			{
				register int	rc;
				fd_set		readSet;
				struct timeval	timeOut;

				readSet = infos->soi_readSet;
				timeOut.tv_sec = 0;
				timeOut.tv_usec = 0;

				rc = select( infos->soi_fd + 1, &readSet, (fd_set *) 0, (fd_set *) 0, &timeOut );

				/* rc = 0 ==> time-out						*/
				/* rc < 0 ==> erreur						*/
				if (rc != 1)
					return (SOCKET_ERR_WAITCLIENT);

				/*
				 * Pas besoin de tester si le descripteur pret en ecoute est bien le
				 * bon, puisqu'on n'en a arme qu'un seul !
				 */
			}

			length = sizeof (struct sockaddr_in);
			result->soi_fd = accept( infos->soi_fd, (struct sockaddr *)&(result->soi_addr), &length );

			if (result->soi_fd == -1)
				return (SOCKET_ERR_WAITCLIENT);

			FD_ZERO(&(result->soi_readSet));
			FD_ZERO(&(result->soi_writeSet));
			FD_SET(result->soi_fd,&(result->soi_readSet));
			FD_SET(result->soi_fd,&(result->soi_writeSet));

			result->soi_port = infos->soi_port;
			result->soi_mode = SOCKET_CLIENT;
			result->soi_open = API_TRUE;
		}
		break;
	}
}

stream_driver_t drv_tcp =
{
	"TCP",
	0, // instance number - auto increment on register ?
	null, // device_context
	null  // instance_context
	system_interrupt_none,
	tcp_init,
	tcp_deinit,
	tcp_open,
	tcp_close,
	null,
	null,
	tcp_read,
	tcp_write,
	null,
	null,
	null
};